package org.altervista.cp.microkernel.persistence.spi;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

import org.altervista.cp.microkernel.persistence.PersistenceException;

public class DefaultPersister<T> extends FieldIntrospector<T> implements
		Persister<T> {

	public DefaultPersister(Class<? extends T> c) {
		super(c);

		String tableName = c.getName().substring(
				c.getName().lastIndexOf('.') + 1);
		StringBuffer fieldNames = new StringBuffer();
		StringBuffer fieldValues = new StringBuffer();
		StringBuffer where = new StringBuffer();
		StringBuffer update = new StringBuffer();
		for (Field f : _fields) {
			if (fieldNames.length() != 0) {
				fieldNames.append(", ");
				fieldValues.append(", ");
				update.append(", ");
			}
			String fname = f.getName().substring(1);
			fieldNames.append(fname);
			fieldValues.append("?");
			update.append(fname + " = ?");
		}
		for (Field f : _idFields) {
			if (fieldNames.length() != 0) {
				fieldNames.append(", ");
				fieldValues.append(", ");
			}
			String fname = f.getName().substring(4);
			fieldNames.append(fname);
			fieldValues.append("?");

			if (where.length() != 0) {
				where.append(", ");
			}
			where.append(fname + " = ?");
		}

		_selectAllSQL = "select " + fieldNames + " from " + tableName;
		_selectSQL = "select " + fieldNames + " from " + tableName + " where "
				+ where;
		_deleteSQL = "delete from " + tableName + " where " + where;
		_insertSQL = "insert into " + tableName + "(" + fieldNames
				+ ") values (" + fieldValues + ")";
		_updateSQL = "update " + tableName + " set " + update + " where "
				+ where;

	}

	public void delete(Connection c, T o) throws SQLException {
		persist(_deleteSQL, c, o);
	}

	public void insert(Connection c, T o) throws SQLException {
		persist(_insertSQL, c, o);
	}

	public T select(Connection c, Object... id) throws SQLException {
		PreparedStatement ps = null;
		try {
			ps = c.prepareStatement(_selectSQL);
			for (int i = 0, n = _idFields.length; i < n; i++) {
				try {
					JDBCType jdbcType = new JDBCType(_idFields[i].getType());
					ps.setObject(i + 1, jdbcType.parse(id[i]), jdbcType
							.getType());
				} catch (Exception e) {
					throw new IllegalStateException(e);
				}
			}
			T[] nis = newInstances(0, ps.executeQuery());
			if (nis.length == 1) {
				return nis[0];
			} else
				throw new PersistenceException(
						"Query statement execution returned no rows (expected: 1)");
		} finally {
			if (ps != null) {
				ps.close();
			}
		}
	}

	public T[] selectAll(Connection c) throws SQLException {
		PreparedStatement ps = null;
		try {
			ps = c.prepareStatement(_selectAllSQL);
			return newInstances(0, ps.executeQuery());
		} finally {
			if (ps != null) {
				ps.close();
			}
		}
	}

	public void update(Connection c, T o) throws SQLException {
		persist(_updateSQL, c, o);
	}

	@SuppressWarnings("unchecked")
	private T[] newInstances(int j, ResultSet rs) throws SQLException {
		if (rs.next()) {
			try {
				T ni = _c.newInstance();
				for (int i = 0, n = _fields.length; i < n; i++) {
					_fields[i].set(ni, rs.getObject(i + 1));
				}
				for (int i = _fields.length, n = _idFields.length; i < n; i++) {
					_idFields[i].set(ni, rs.getObject(i + 1));
				}
				T[] nis = newInstances(j + 1, rs);
				nis[j] = ni;
				return nis;
			} catch (SQLException e) {
				throw e;
			} catch (Exception e) {
				throw new IllegalStateException(e);
			}
		} else {
			rs.close();
			return (T[]) Array.newInstance(_c, j);
		}
	}

	private void persist(String sql, Connection c, T o) throws SQLException {
		PreparedStatement ps = null;
		try {
			ps = c.prepareStatement(sql);
			if (!sql.startsWith("delete")) {
				setParameters(0, _fields, ps, o);
				setParameters(_fields.length, _idFields, ps, o);
			} else {
				setParameters(0, _idFields, ps, o);
			}
			int rows = ps.executeUpdate();
			if (rows != 1)
				throw new PersistenceException(
						"Persister statement execution returned " + rows
								+ " rows (expected: 1)");
		} finally {
			if (ps != null) {
				ps.close();
			}
		}

	}

	private void setParameters(int startIndex, Field[] flds,
			PreparedStatement ps, T o) {
		for (int i = 0, n = flds.length; i < n; i++) {
			try {
				Object value = flds[i].get(o);
				JDBCType jdbcType = new JDBCType(flds[i].getType());
				ps.setObject(i + startIndex + 1, jdbcType.parse(value),
						jdbcType.getType());
			} catch (Exception e) {
				throw new IllegalStateException(e);
			}
		}
	}

	private void setFieldNames(int i, int j, int k, Field[] fields) {
		if (i == fields.length) {
			if (k == 0)
				throw new IllegalArgumentException(
						"One id field must be supplied at least");
			_fields = new Field[j];
			_idFields = new Field[k];
		} else {
			String fn = fields[i].getName();
			if (Modifier.isPrivate(fields[j].getModifiers())
					&& fn.startsWith("_")) {
				if (fn.startsWith("_pk_")) {
					setFieldNames(i + 1, j, k + 1, fields);
					_idFields[j] = fields[i];
				} else {
					setFieldNames(i + 1, j + 1, k, fields);
					_fields[j] = fields[i];
				}
			} else {
				setFieldNames(i + 1, j, k, fields);
			}

		}
	}

	private String _deleteSQL;
	private String _insertSQL;
	private String _selectSQL;
	private String _selectAllSQL;
	private String _updateSQL;

}
